package com.ubb.dbms.core.parser;

import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserManager;
import net.sf.jsqlparser.statement.Statement;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.ubb.dbms.core.Utils.SQLExceptions.ItemAlreadyExistsException;
import com.ubb.dbms.core.Utils.SQLExceptions.ItemNotExistsException;
import com.ubb.dbms.core.connection.result.MessageResult;
import com.ubb.dbms.core.fileManagers.indexFileManager.IndexFileManager;
import com.ubb.dbms.core.operatorEvaluator.OperatorEvaluator;
import com.ubb.dbms.core.operatorEvaluator.OperatorEvaluatorImpl;
import com.ubb.dbms.core.parser.visitors.SQLStatementVisitor;
import com.ubb.dbms.domain.xml.Database;
import com.ubb.dbms.domain.xml.IAttribute;
import com.ubb.dbms.domain.xml.IndexAttributes;
import com.ubb.dbms.domain.xml.IndexFile;


public class SQLQueryParser implements QueryParser {
	private static String databaseName = null;
	private final Logger log = LoggerFactory.getLogger(SQLQueryParser.class);
	private CCJSqlParserManager parserManager;
	private static final OperatorEvaluator operatorEvaluator = new OperatorEvaluatorImpl();

	public SQLQueryParser() {
		parserManager = new CCJSqlParserManager();
	}
	
	@Override
	public void commit(String query) {
        List<String> queryList = new ArrayList<String>(Arrays.asList(query.split(";")));

        for (String singleQuery : queryList) {
        	try {
            	Statement statement = parserManager.parse(new StringReader(singleQuery));
        		
            	statement.accept(new SQLStatementVisitor());
            	
        	} catch (JSQLParserException e) {
        		if (!handleNonJSqlParserStatement(singleQuery)) {
//        			log.error(e.getCause().getMessage());
        			log.error("Parsing error on query: " + singleQuery);
        			MessageResult.sendMessage(MessageResult.MESSAGE_ERROR, "Parsing error on query: " + singleQuery);
        		}
			}
        }
	}
	
	/**
	 * Handling CREATE DATABASE, USE
	 * @param singleQuery a statement from the client
	 * @return true if the query was parsed correctly, else false
	 */
	private boolean handleNonJSqlParserStatement(String singleQuery) {
		singleQuery = singleQuery.replaceAll("\\s+", " "); // remove duplicated whitespaces
		singleQuery = singleQuery.trim(); // remove leading and trailing whitespaces
		if (singleQuery.toUpperCase().startsWith("CREATE DATABASE ")){
			createDatabaseQuery(singleQuery);
			return true;
		} 
		else if (singleQuery.toUpperCase().startsWith("CREATE INDEX ")) {
			return createIndexQuery(singleQuery);
		}
		else if (singleQuery.toUpperCase().startsWith("USE ")) {
			useDatabase(singleQuery);
			return true;
		}
		return false;
	}
	
	private void createDatabaseQuery(String singleQuery) {
		String[] parsedQuery = singleQuery.split("\\s");
		String databaseName = parsedQuery[2];
		Database database = new Database();
		database.setDatabaseName(databaseName);
		// calling method of operatorEvaluator with "database" domain object
		try {
			log.info("CREATE DATABASE");
			operatorEvaluator.manageCreateDatabase(database);
			MessageResult.sendMessage(MessageResult.MESSAGE_INFO, "Database created");
		} catch (ItemAlreadyExistsException e) {
			log.error("Database already exists");
			MessageResult.sendMessage(MessageResult.MESSAGE_ERROR, "Database already exists");
		}
	}
	
	private boolean createIndexQuery(String singleQuery) {
		String[] parsedQuery = singleQuery.split("\\s");
		if (parsedQuery.length < 3) return false;
		if (parsedQuery[2].toUpperCase().equals("ON")) { // single column index
			String parsedTableName = "", parsedColumnName = "";
			String nonSplitTableData;
			switch (parsedQuery.length){
			// no whitespace between Table Name and paranthesed Column Name
			// (e.g. CREATE INDEX on disciplines(DiscID))
			case 4:
				nonSplitTableData = parsedQuery[3].replaceAll("\\(|\\)", " ");
				String[] parsedData = nonSplitTableData.trim().split("\\s");
				if (parsedData.length == 2) {
					parsedTableName = parsedData[0];
					parsedColumnName = parsedData[1];
				} else {
					return false;
				}
				break;
			// (e.g. CREATE INDEX on disciplines (DiscID))
			case 5:
				nonSplitTableData = parsedQuery[4].replaceAll("\\(|\\)", " ");
				parsedData = nonSplitTableData.trim().split("\\s");
				if (parsedData.length == 1) {
					parsedTableName = parsedQuery[3];
					parsedColumnName = parsedData[0];
				} else {
					return false;
				}
				break;
			default:
				return false;
			}
			IndexFile indexFile = new IndexFile();
			indexFile.setIndexFileName(parsedColumnName + IndexFileManager.INDEX_FILES_EXTENSION);
			indexFile.setIndexName(parsedColumnName);
			indexFile.setIndexType(IndexFile.B_PLUS_TREE_INDEX_TYPE);
			indexFile.setIsUnique("1");
			// TODO Checker -> indexFile.setKeyLenght()
			IAttribute attribute = new IAttribute();
			attribute.setValue(parsedColumnName);
			
			IndexAttributes indexAttributes = new IndexAttributes();
			indexAttributes.setIndexAttributeList(new ArrayList<IAttribute>());
			indexAttributes.getIndexAttributeList().add(attribute);
			
			indexFile.setIndexAttribute(indexAttributes);

			try {
				log.info("CREATE INDEX");
				operatorEvaluator.manageCreateIndex(indexFile, parsedTableName, databaseName);
				MessageResult.sendMessage(MessageResult.MESSAGE_INFO, "Index created");
				return true;
			} catch (ItemNotExistsException e) {
				log.error("Create Index error");
				MessageResult.sendMessage(MessageResult.MESSAGE_ERROR, "Create index error");
			}
		} else { // Mixed column index (parsedQuery[2] is the name of the mixed index)
			return false;
		}
		return false;
	}
	
	private void useDatabase(String singleQuery) {
		String[] parsedQuery = singleQuery.split("\\s");
		databaseName = parsedQuery[1];
		operatorEvaluator.manageUseDatabase(databaseName);
		MessageResult.sendMessage(MessageResult.MESSAGE_INFO, "database selected "+ databaseName);
	}

	public static String getDatabaseName() {
		return databaseName;
	}
	
	public static OperatorEvaluator getOperatorEvaluator() {
		return operatorEvaluator;
	}
}
